package james.gan.utils;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

public class ClassUtils {

  public static List<?> getPkgClassInstance(String pkgName, boolean isRecursive,
      Class<? extends Annotation> annotation)throws ClassNotFoundException,IOException {
    return instanceClass(getClassList(pkgName, isRecursive, annotation));
  }
  
  public static List<?> instanceClass(List<Class<?>> clss) {
    return clss.stream().map(i -> {
      try {
        return i.newInstance();
      } catch (Exception e) {
        return null;
      }
    }).filter(i -> i != null).collect(Collectors.toList());
  }

  /**
   * 鐢ㄤ簬鑾峰彇鎸囧畾鍖呭悕涓嬬殑鎵�鏈夌被鍚�.<br/>
   * 骞跺彲璁剧疆鏄惁閬嶅巻璇ュ寘鍚嶄笅鐨勫瓙鍖呯殑绫诲悕.<br/>
   * 骞跺彲閫氳繃Annotation(鍐呮敞)鏉ヨ繃婊わ紝閬垮厤涓�浜涘唴閮ㄧ被鐨勫共鎵�.<br/>
 * @throws IOException 
 * @throws ClassNotFoundException 
   */
  public static List<Class<?>> getClassList(String pkgName, boolean isRecursive,
      Class<? extends Annotation> annotation) throws IOException, ClassNotFoundException {
    List<Class<?>> classList = new ArrayList<Class<?>>();
    ClassLoader loader = Thread.currentThread().getContextClassLoader();
      // 鎸夋枃浠剁殑褰㈠紡鍘绘煡鎵�
      String strFile = pkgName.replaceAll("\\.", "/");
      Enumeration<URL> urls = loader.getResources(strFile);
      while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        if (url != null) {
          String protocol = url.getProtocol();
          String pkgPath = url.getPath();
          if ("file".equals(protocol)) {
            // 鏈湴鑷繁鍙鐨勪唬鐮�
            findClassName(classList, pkgName, pkgPath, isRecursive, annotation);
          } else if ("jar".equals(protocol)) {
            // 寮曠敤绗笁鏂筳ar鐨勪唬鐮�
            findClassName(classList, pkgName, url, isRecursive, annotation);
          }
        }
      }
    return classList;
  }

  public static void findClassName(List<Class<?>> clazzList, String pkgName, String pkgPath,
      boolean isRecursive, Class<? extends Annotation> annotation) throws ClassNotFoundException {
    if (clazzList == null) {
      return;
    }
    File[] files = filterClassFiles(pkgPath);// 杩囨护鍑�.class鏂囦欢鍙婃枃浠跺す
    if (files != null) {
      for (File f : files) {
        String fileName = f.getName();
        if (f.isFile()) {
          // .class 鏂囦欢鐨勬儏鍐�
          String clazzName = getClassName(pkgName, fileName);
          addClassName(clazzList, clazzName, annotation);
        } else {
          // 鏂囦欢澶圭殑鎯呭喌
          if (isRecursive) {
            // 闇�瑕佺户缁煡鎵捐鏂囦欢澶�/鍖呭悕涓嬬殑绫�
            String subPkgName = pkgName + "." + fileName;
            String subPkgPath = pkgPath + "/" + fileName;
            findClassName(clazzList, subPkgName, subPkgPath, true, annotation);
          }
        }
      }
    }
  }

  /**
   * 绗笁鏂笿ar绫诲簱鐨勫紩鐢ㄣ��<br/>
   * 
   * @throws IOException
 * @throws ClassNotFoundException 
   */
  public static void findClassName(List<Class<?>> clazzList, String pkgName, URL url,
      boolean isRecursive, Class<? extends Annotation> annotation) throws IOException, ClassNotFoundException {
    JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
    JarFile jarFile = jarURLConnection.getJarFile();
    Enumeration<JarEntry> jarEntries = jarFile.entries();
    while (jarEntries.hasMoreElements()) {
      JarEntry jarEntry = jarEntries.nextElement();
      String jarEntryName = jarEntry.getName(); // 绫讳技锛歴un/security/internal/interfaces/TlsMasterSecret.class
      String clazzName = jarEntryName.replace("/", ".");
      int endIndex = clazzName.lastIndexOf(".");
      String prefix = null;
      if (endIndex > 0) {
        String prefix_name = clazzName.substring(0, endIndex);
        endIndex = prefix_name.lastIndexOf(".");
        if (endIndex > 0) {
          prefix = prefix_name.substring(0, endIndex);
        }
      }
      if (prefix != null && jarEntryName.endsWith(".class")) {
        // System.out.println("prefix:" + prefix +" pkgName:" + pkgName);
        if (prefix.equals(pkgName)) {
          addClassName(clazzList, clazzName, annotation);
        } else if (isRecursive && prefix.startsWith(pkgName)) {
          // 閬嶅巻瀛愬寘鍚嶏細瀛愮被
          addClassName(clazzList, clazzName, annotation);
        }
      }
    }
  }

  private static File[] filterClassFiles(String pkgPath) {
    if (pkgPath == null) {
      return null;
    }
    // 鎺ユ敹 .class 鏂囦欢 鎴� 绫绘枃浠跺す
    return new File(pkgPath).listFiles(new FileFilter() {
      @Override
      public boolean accept(File file) {
        return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
      }
    });
  }

  private static String getClassName(String pkgName, String fileName) {
    int endIndex = fileName.lastIndexOf(".");
    String clazz = null;
    if (endIndex >= 0) {
      clazz = fileName.substring(0, endIndex);
    }
    String clazzName = null;
    if (clazz != null) {
      clazzName = pkgName + "." + clazz;
    }
    return clazzName;
  }

  private static void addClassName(List<Class<?>> clazzList, String clazzName,
      Class<? extends Annotation> annotation) throws ClassNotFoundException {
    if (clazzList != null && clazzName != null) {
      Class<?> clazz = null;
        clazz = Class.forName(clazzName);
      // System.out.println("isAnnotation=" + clazz.isAnnotation() +" author:" +
      // clazz.isAnnotationPresent(author.class));

      if (clazz != null) {
        if (annotation == null) {
          clazzList.add(clazz);
        } else if (clazz.isAnnotationPresent(annotation)) {
          clazzList.add(clazz);
        }
      }
    }
  }
}
